博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
架构探险笔记2
阅读量:6302 次
发布时间:2019-06-22

本文共 19655 字,大约阅读时间需要 65 分钟。

系统设计

一个web项目,先从原始需求开始分析,找出需求中涉及到的Use Case(案例),然后涉及表结构,画原型图,定义URL规范。

1.设计用例

找出功能点,可以用一张UML的”用例图“来描绘以上用例,这样效果会更好,UML流程图可以用visio画图。

2.设计表结构

根据需求,找到核心的业务实体,创建对应的表。

建议:

表明与字段名均为小写,若多个单词可用“下划线”分割;

每张表都要有一个唯一ID主键字段

数据类型尽可能统一,不要出现太多数据类型

个人一般比较喜欢用powerdesigner,设计好表后可以直接生成sql语句。

3.设计界面原型

可以使用Balsqmiq Mockups软件,这是一款比较Q的软件,可以快速地画出界面原型,感受一下画风。

(这个画风让我想起了饥荒这款游戏。。。。。)

 4.设计URL

设计出url与页面跳转及接口的对应关系

举个栗子

URL  描述
GET:/customer 进入列表查询界面
GET:/customer_create 进入新增界面
POST:/customer_create 新增
DELETE:/customer_delete/{id}  删除

创建数据库

创建数据库编码方式统一为UTF-8,以免编码不一致导致中文乱码。

建立连接后,New Database输入Database Name和Character set,点击确定。

这里使用的工具为Navicat。

准备开发环境

新建一个maven项目,具体步骤参照。 

这里不用框架,用servlet+jsp写一个mvc架构的项目。

编写模型层

 

数据库中添加表

CREATE TABLE `customer`(    `id` BIGINT(20) NOT NULL AUTO_INCREMENT,    `name` VARCHAR(255) DEFAULT NULL,    `contact` VARCHAR(255) DEFAULT NULL,    `telephone` VARCHAR(255) DEFAULT NULL,    `email` VARCHAR(255) DEFAULT NULL,    `remark` text,     PRIMARY KEY(`id`))ENGINE=INNODB DEFAULT CHARSET=utf8;

编写控制器层

一个servlet只有一个请求路径,但可以处理多种不同的请求,请求类型有GET、POST、PUT、DELETE

这里因为是servlet3.0所以不用在web.xml中配置servlet及映射mapping,只需要用@WebServlet注解即可

编写服务层

package com.smart4j.chapter2.service;/** * Created by Administrator on 2018/8/13. */import com.smart4j.chapter2.model.Customer;import java.util.List;import java.util.Map;/** * @program: chapter2->CustomerService * @description: 客户服务层 * @author: qiuyu * @create: 2018-08-13 20:15 **/public class CustomerService {        /**     * @Description: 获取客户列表    * @Param: []     * @return: java.util.List
* @Author: qiuyu * @Date: 2018/8/13 */ public List
getCustomerList(String keyWord){ //TODO return null; } /** * @Description: 根据id获取客户 * @Param: [id] * @return: com.smart4j.chapter2.model.Customer * @Author: qiuyu * @Date: 2018/8/13 */ public Customer getCustomer(long id){ //todo return null; } /** * @Description: 创建客户 * @Param: [fieldMap] * @return: boolean * @Author: qiuyu * @Date: 2018/8/13 */ public boolean createCustomer(Map
fieldMap){ //todo return false; } /** * @Description: 更新客户 * @Param: [] * @return: boolean * @Author: qiuyu * @Date: 2018/8/13 */ public boolean updateCustomer(long id,Map
fieldMap){ //todo return false; } /** * @Description: 删除客户 * @Param: [] * @return: boolean * @Author: qiuyu * @Date: 2018/8/13 */ public boolean deleteCustomer(long id){ //todo return false; } }

编写单元测试

首先要在pom.xml中添加依赖

junit
junit
4.11
test

编写单元测试

/** * Created by Administrator on 2018/8/13. */import com.smart4j.chapter2.model.Customer;import com.smart4j.chapter2.service.CustomerService;import org.junit.Assert;import org.junit.Before;import org.junit.Test;import java.util.HashMap;import java.util.List;import java.util.Map;/** * @program: chapter2->CustomerServiceText * @description: 客户测试类 * @author: qiuyu * @create: 2018-08-13 20:24 **/public class CustomerServiceText {    private final CustomerService customerService;    public CustomerServiceText() {        customerService = new CustomerService();    }    @Before   public void init(){        //初始化数据库   }   @Test   public void getCustomerListText(){       List
customerList =customerService.getCustomerList(""); Assert.assertEquals(2,customerList.size()); } @Test public void getCustomerTest(){ long id =1; Customer customer = customerService.getCustomer(id); Assert.assertNotNull(customer); } @Test public void createCustomerTest(){ Map
fieldMap = new HashMap
(); fieldMap.put("name","小猪佩琪"); fieldMap.put("contact","John"); fieldMap.put("telephone","18151449650"); boolean result = customerService.createCustomer(fieldMap); Assert.assertTrue(result); } @Test public void updateCustomerTest(){ long id =1; Map
fieldMap = new HashMap
(); fieldMap.put("contact","Aeolian"); boolean result = customerService.updateCustomer(id,fieldMap); Assert.assertTrue(result); } @Test public void deleteCustomer(){ long id =1; boolean result = customerService.deleteCustomer(id); Assert.assertTrue(result); }}

视图层

使用JSP充当视图层,在WEB-INF/view目录下存放所有的JSP文件。推荐将JSP放入到WEB-INF内部,因为用户无法通过浏览器地址栏直接请求放在WEB-INF内部的JSP文件,必须通过Servlet进行转发或者重定向。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>    客户管理-创建客户

创建客户界面

<%--TODO--%>

日志 

在pom.xml中添加日志依赖

org.slf4j
slf4j-log4j12
1.77

在main/resource目录下新建一个名为log4j.properties的文件

log4j.rootLogger=ERROR,console,filelog4j.appender.console=org.apache.log4j.ConsoleAppenderlog4j.appender.console.layout=org.apache.log4j.PatternLayoutlog4j.appender.console.layout.ConversionPattern=%m%nlog4j.appender.file=org.apache.log4j.DailyRollingFileAppenderlog4j.appender.file.File=${user.home}/logs/book.loglog4j.appender.file.DatePattern='_'yyyyMMddlog4j.appender.file.layout=org.apache.log4j.PatternLayoutlog4j.appender.file.layout.ConversionPattern=%d{HH:mm:ss,SSS} %p %c (%L) -%m %nlog4j.logger.org.smart4j=DEBUG

将日志级别设置为DEBUG,并提供了两种日志appender,分别是console与file。最后一句制定只有org.smart4j包下的类才能输出DEBUG级别的日志。

测试,这里user.home为C:\Users\Administrator

public class PropsUtil {    private static final Logger LOGGER = LoggerFactory.getLogger(PropsUtil.class);    public static void main(String[] args) throws IOException {        /*测试日志*/        LOGGER.error("text");    }}

数据库

添加mysql依赖以及两个apache常用依赖

mysql
mysql-connector-java
5.1.33
runtime
org.apache.commons
commons-lang3
3.3.2
org.apache.commons
commons-collections4
4.0

配置confg.properties

jdbc.driver=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://******:3306/demo2?characterEncoding=utf-8jdbc.username=rootjdbc.password=***密码****jdbc.wait_timeout=600

编写常用工具类properties文件读写、类型转换、字符串工具类、集合工具类

详细链接

数据库操作类

数据库操作类DBHelper.java,用于初始化数据库,打开连接,关闭连接。 

package com.smart4j.chapter.util;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;import java.util.Properties;/** * @program: DBHelper * @description: 数据库操作类 **/public class DBHelper {    private static final Logger LOGGER = LoggerFactory.getLogger(DBHelper.class);    private static final String DRIVER;    private static final String URL;    private static final String USERNAME;    private static final String PASSWORD;    /**     * 静态代码块在类加载时运行     */    static{        Properties conf = PropsUtil.loadProps("config.properties");        DRIVER = conf.getProperty("jdbc.driver");        URL = conf.getProperty("jdbc.url");        USERNAME = conf.getProperty("jdbc.username");        PASSWORD = conf.getProperty("jdbc.password");        try {            Class.forName(DRIVER);        } catch (ClassNotFoundException e) {            //e.printStackTrace();            LOGGER.error("can not load jdbc driver",e);        }    }    /**     * 获取数据库连接     * @return     */    public static Connection getConnection(){        Connection conn = null;        try {            conn = DriverManager.getConnection(URL,USERNAME,PASSWORD);        } catch (SQLException e) {            e.printStackTrace();  //在catlina.out中打印            LOGGER.error("get connection failure",e);        }        return conn;    }    /**     * 关闭数据库连接     * @param conn     */    public static void closeConnection(Connection conn){        if (conn!=null){            try {                conn.close();            } catch (SQLException e) {                e.printStackTrace();                LOGGER.error("close connection failure",e);            }        }    }}

 使用Apache Common项目中的DbUtils类库,pom.xml中插入依赖

commons-dbutils
commons-dbutils
1.6

然后在DBHelper类中增添以下方法。

private static final QueryRunner QUERY_RUNNER = new QueryRunner();    /**     * 查询实体列表     * @param entityClass     * @param sql     * @param params     * @param 
* @return */ public static
List
queryEntityList(Class
entityClass,String sql,Object... params){ List
entityList; Connection conn = getConnection(); try { entityList = QUERY_RUNNER.query(conn,sql,new BeanListHandler
(entityClass),params); } catch (SQLException e) { //e.printStackTrace(); LOGGER.error("query entity list failure",e); throw new RuntimeException(e); } finally { closeConnection(conn); } return entityList; } /** * 查询实体 * @param entityClass * @param sql * @param params * @param
* @return */ public static
T queryEntity(Class
entityClass,String sql,Object... params){ T entity; Connection conn = getConnection(); try { entity=QUERY_RUNNER.query(conn,sql,new BeanHandler
(entityClass),params); } catch (SQLException e) { //e.printStackTrace(); LOGGER.error("query entity failure",e); throw new RuntimeException(e); } finally { closeConnection(conn); } return entity; } /** * 多表查询,其中的Map表示列明与列值的映射关系 * @param sql * @param params * @return */ public static List
> executeQuery(String sql,Object... params){ List
> result; Connection conn = getConnection(); try { result = QUERY_RUNNER.query(conn,sql,new MapListHandler(),params); } catch (SQLException e) { //e.printStackTrace(); LOGGER.error("execute query failure",e); throw new RuntimeException(e); } return result; } /** * 执行更新语句(包括update,insert,delete) * @param sql * @param params * @return */ public static int executeUpdate(String sql,Object... params){ int rows =0; Connection conn = getConnection(); try { rows = QUERY_RUNNER.update(conn,sql,params); } catch (SQLException e) { //e.printStackTrace(); LOGGER.error("execute update failure",e); throw new RuntimeException(e); } finally { closeConnection(conn); } return rows; } /** * 插入实体(根据executeUpdate方法) * @param entityClass * @param fieldMap * @param
* @return */ public static
boolean insertEntity(Class
entityClass,Map
fieldMap){ if (CollectionUtil.isEmpty(fieldMap)){ LOGGER.error("can not insert entity: fieldMap is empty"); return false; } String sql = "insert into "+getTableName(entityClass); StringBuilder columns = new StringBuilder("("); StringBuilder values = new StringBuilder("("); for (String fieldName:fieldMap.keySet()){ columns.append(fieldName).append(", "); values.append("?, "); } columns.replace(columns.lastIndexOf(", "),columns.length(),")"); values.replace(values.lastIndexOf(", "),values.length(),")"); sql += columns+" VALUES" +values; Object[] params = fieldMap.values().toArray(); return executeUpdate(sql,params)==1; } /** * 修改 * @param entityClass * @param id * @param fieldMap * @param
* @return */ public static
boolean updateEntity(Class
entityClass,long id,Map
fieldMap){ if (CollectionUtil.isEmpty(fieldMap)){ LOGGER.error("can not update entity: fieldMap is empty"); return false; } String sql = "update "+getTableName(entityClass) + " set "; StringBuilder columns = new StringBuilder(); for (String fieldName:fieldMap.keySet()){ columns.append(fieldName).append("=?, "); } sql+= columns.substring(0,columns.lastIndexOf(", "))+"where id=?"; List
paramList = new ArrayList(); paramList.addAll(fieldMap.values()); paramList.add(id); Object[] params = paramList.toArray(); return executeUpdate(sql,params)==1; } /** * 删除 * @param entityClass * @param id * @param
* @return */ public static
boolean deleteEntity(Class
entityClass,long id){ String sql = "delete from "+getTableName(entityClass)+" where id =?"; return executeUpdate(sql,id)==1; } /** * 获取类名(不包含报名和后缀) * @param entityClass * @return */ private static String getTableName(Class
entityClass){ return entityClass.getSimpleName(); }

数据库连接池 

为了不频繁的创建和关闭连接浪费资源,我们使用数据库连接池Apache DBCP。

pom.xml中加入

org.apache.commons
commons-dbcp2
2.0.1

DBHelper.java,用连接池获取conn,把所有的关闭连接池方法注释掉。

/*数据库连接池*/    private static final ThreadLocal
CONNECTION_HOLDER; private static final BasicDataSource DATA_SOURCE; /** * 静态代码块在类加载时运行 */ static{ Properties conf = PropsUtil.loadProps("config.properties"); DRIVER = conf.getProperty("jdbc.driver"); URL = conf.getProperty("jdbc.url"); USERNAME = conf.getProperty("jdbc.username"); PASSWORD = conf.getProperty("jdbc.password"); //数据库连接池 CONNECTION_HOLDER = new ThreadLocal
(); DATA_SOURCE = new BasicDataSource(); DATA_SOURCE.setDriverClassName(DRIVER); DATA_SOURCE.setUrl(URL); DATA_SOURCE.setUsername(USERNAME); DATA_SOURCE.setPassword(PASSWORD); /*使用连接池就不需要jdbc加载驱动了*/ /*try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { //e.printStackTrace(); LOGGER.error("can not load jdbc driver",e); }*/ } /** * 获取数据库连接 * @return */ public static Connection getConnection(){ Connection conn = null; conn = CONNECTION_HOLDER.get(); if (conn==null){ //当从连接池中获取的connection为null时新建一个Connection try { /*JDBC获取连接*/ //conn = DriverManager.getConnection(URL,USERNAME,PASSWORD); /*数据库连接池获取连接*/ conn = DATA_SOURCE.getConnection(); } catch (SQLException e) { e.printStackTrace(); //在catlina.out中打印 LOGGER.error("get connection failure",e); } finally { /*数据库连接池,新建的情况要把新建的connection放入到池中*/ CONNECTION_HOLDER.set(conn); } } return conn; }

测试 

连接池写好,下面我们测试一下,这里存在一个问题,就是执行deleteCustomerTest方法就不能在执行getCustomerTest方法了。因为得到的结果受到了影响。

JUnit在调用每个@Test方法前,都会调用@Before方法,也就是我们在单元测试类里定义的init,先在这个方法里加个TODO。需要在这里准备一个测试的数据库。

为了使开发数据库与测试数据库分离,也就是说,应该是两个数据库,只是表结构相同而已。我们需要为单元测试创建一个数据库,命名为demo_test,需要将demo_test数据库的customer表复制到demo_test数据库中。

新建测试数据库具体操作:

1.进入demo数据库,选中customer表,使用Ctrl+C复制表。

2.进入到demo_test数据库,使用Ctrl+V粘贴表。

3.右击customer表,单机Truncate Table清空表数据

准备一个customer_init.sql文件,用于存放所有的insert语句,改文件放在test/resources/sql下面,具体内容如下

TRUNCATE customer;INSERT INTO `customer`(`name`, `contact`, `telephone`, `email`, `remark`) VALUES ('customer1', 'Jack', '18151449650', 'jack@gmail.com', NULL);INSERT INTO `customer`(`name`, `contact`, `telephone`, `email`, `remark`) VALUES ('customer2', 'Rose', '13654565445', 'rose@gmail.com', NULL);

先Truncate清空表数据,然后再执行insert语句,不需要提供id,因为id是自增的。

技巧:默认情况下,IDEA不会在test下创建resources目录,可用alt+insert快捷键创建resources目录。右键该目录,单击Mark Directory As/Resources Root,该目录设为Maven的测试资源目录。需要注意的是,main/java、main/resources、test/java、test/resources四个目录都是classpath的根目录,当运行单元测试时,遵循“就近原则”,即有先从test/java、test/resources加载类或读取文件

在test/resources目录下提供一个config.properties文件,这样执行单元测试的时候会优先在test/resources中找配置文件,若没有,再去main/resources中找。

jdbc.driver=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://*****:3306/****_test?characterEncoding=utf-8jdbc.username=rootjdbc.password=******jdbc.wait_timeout=600

 DBHelper.java中加入执行sql文件的方法

/**     * 逐行执行sql文件     * @param filePath     */    public static void executeSqlFile(String filePath){        //获取sql文件的InputStream流        InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath);        //将InputStream流转为Reader        BufferedReader reader = new BufferedReader(new InputStreamReader(is));        try {            String sql;            while ((sql = reader.readLine())!=null){  //逐行读取                executeUpdate(sql);  //执行读取出来的一行sql语句            }        } catch (IOException e) {            //e.printStackTrace();            LOGGER.error("execute sql file failure",e);            throw new RuntimeException(e);        }    }

在@before的方法中运行sql文件确保每次执行的测试数据库中的数据都是初始的。

@Before   public void init() throws IOException {        DBHelper.executeSqlFile("sql/customer_init.sql");   }

测试技巧:把光标放在方法外部,运行Run(Shift+F10)或Debug(Shift+F9),可测试所有方法。如果在某个测试方法内,智能执行光标所在的方法。

完善Controller层

/** * @description: 进入客户列表界面 **/@WebServlet("/customer")public class CustomerServlet extends HttpServlet{    private CustomerService service;    @Override    public void init() throws ServletException {        super.init();        service = new CustomerService();    }    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        List
customerList = service.getCustomerList(""); req.setAttribute("customerList",customerList); req.getRequestDispatcher("/WEB-INF/view/customer.jsp").forward(req,resp); }}

在CustomerServlet中定义一个CustomerService变量,并在init方法中进行初始化。这样可以避免创建多个service实例,其实整个web应用只需要一个CustomerService实例。后续可以使用"单例模式"进行优化

在doGet方法中,我们调用了CustomerService对象的getCustomerList方法来获取List对象,并将其放入请求属性中,最后通过请求对象重定向到customer.jsp视图。

完善视图层

<%@ page pageEncoding="utf-8" contentType="text/html;charset=UTF-8" language="java" %><%@ taglib prefix="c" uri="http://java.sun.com/jstl/core"%>
客户管理

客户列表

客户名称 联系人 电话号码 邮箱地址 操作
${customer.name} ${customer.contact} ${customer.telephone} ${customer.email} 编辑 删除

技巧:IDEA中查找文件的三种方法:1.Ctrl+N根据java类名查找文件;2.Ctrl+Shift+N,根据任意文件名进行查找;3.在Project目录上,Ctrl+Shift+F在指定路径下进行查找。

 

每一个请求都要对应一个Servlet,随着需求增多,Servlet类会越来越多。如何把多个业务模块的Servlet合并到一个类里面,例如:

@Controllerpublic class CustomerController {    private CustomerService customerService;    /**     * 进入客户端界面     */    @Action("get:/customer")    public View index(){        List
customerList = customerService.getCustomerList(); return new View("customer.jsp").addModel("customerList",customerList); } /** * 显示客户基本信息 */ @Action("get:/customer_show") public View show(Param param){ long id = param.getLong(id); Customer customer = customerService.getCustomer(id); return new View("customer_show.jsp").addModel("customer",customer); } /** * 删除客户信息 * @param param * @return */ @Action("delete:/customer_edit") public Data delete(Param param){ long id = param.getLong(id); boolean result = customerService.deleteCustomer(id); return new Data(result); }}

1.在类控制上,使用Controller注解,表明该类是一个控制器。

2.在成员变量上,使用Inject注解,自动创建该成员变量的实例

3.在控制类中包含若干方法(称为Action),每个方法都会使用Get/Post/Put/Delete注解,用于指定”请求类型“与”请求路径“。

4.在Action的参数中,通过Param对象封装所有请求参数,可根据getLong、getFieldMap等方法

5.在Action的返回值中,使用View封装一个JSP页面,使用Data封装一个Json数据。

有了这一个Controller,Servlet数量瞬间降下来。这种方式将MVC架构变得更加轻量级。如何开发架构,

 

转载于:https://www.cnblogs.com/aeolian/p/9457695.html

你可能感兴趣的文章
初识闭包
查看>>
hdu1874畅通工程续
查看>>
rails 字符串 转化为 html
查看>>
java-学习8
查看>>
AOP动态代理
查看>>
Yii2.0 下的 load() 方法的使用
查看>>
华为畅玩5 (CUN-AL00) 刷入第三方twrp Recovery 及 root
查看>>
[转] ReactNative Animated动画详解
查看>>
DNS原理及其解析过程
查看>>
没想到cnblog也有月经贴,其实C#值不值钱不重要。
查看>>
【转】LUA内存分析
查看>>
[转] Entity Framework Query Samples for PostgreSQL
查看>>
软件需求分析的重要性
查看>>
UVA465:Overflow
查看>>
HTML5-placeholder属性
查看>>
Android选择本地图片过大程序停止的经历
查看>>
poj 2187:Beauty Contest(旋转卡壳)
查看>>
《Flask Web开发》里的坑
查看>>
Python-库安装
查看>>
Git笔记
查看>>